package zjl.graph;

import zjl.util.Pair;

import java.util.*;

public class Graph implements BaseGraph {

    public static final double DISCONNECTED = Double.MAX_VALUE;

    // the number of vertices in the graph
    protected int vertexNum = 0;

    // the number of egdes in the graph
    protected int edgeNum = 0;

    // index for vertices in the graph
    protected Map<Integer, BaseVertex> idVertexIndex = new HashMap<>();

    // index for edges in the graph
    protected Map<Integer, BaseEdge> idEdgeIndex = new HashMap<>();

    // index for fan-outs of one vertex
    protected Map<Integer, Set<BaseVertex>> fanoutVerticesIndex = new HashMap<>();

    // index for fan-ins of one vertex
    protected Map<Integer, Set<BaseVertex>> faninVerticesIndex = new HashMap<>();

    // vertex list
    protected List<BaseVertex> vertexList = new ArrayList<>();

    // edge list
    protected List<BaseEdge> edgeList = new ArrayList<>();

    // vertex set to be removed
    private Set<BaseVertex> remVertexSet = new HashSet<>();

    // edge set to be removed
    private Set<BaseEdge> remEdgeSet = new HashSet<>();

    public Graph() { }

    public Graph(Graph graph) {
        this.vertexNum = graph.vertexNum;
        this.edgeNum = graph.edgeNum;
        this.vertexList.addAll(graph.vertexList);
        this.edgeList.addAll(graph.edgeList);
        this.idVertexIndex.putAll(graph.idVertexIndex);
        this.idEdgeIndex.putAll(graph.idEdgeIndex);
        this.faninVerticesIndex.putAll(graph.faninVerticesIndex);
        this.fanoutVerticesIndex.putAll(graph.fanoutVerticesIndex);
        this.remEdgeSet.addAll(graph.remEdgeSet);
        this.remVertexSet.addAll(graph.remVertexSet);
    }

    public void clear() {
        Edge.reset();
        Vertex.reset();
        this.vertexNum = 0;
        this.edgeNum = 0;
        this.vertexList.clear();
        this.edgeList.clear();
        this.idEdgeIndex.clear();
        this.idVertexIndex.clear();
        this.faninVerticesIndex.clear();
        this.fanoutVerticesIndex.clear();
        this.remEdgeSet.clear();
        this.remVertexSet.clear();
    }

    public Set<BaseVertex> getAdjacentVerticesOfOriginGraph(BaseVertex vertex) {
        return this.fanoutVerticesIndex.containsKey(vertex.getId())
                ? this.fanoutVerticesIndex.get(vertex.getId())
                : new HashSet<BaseVertex>();
    }

    @Override
    public Set<BaseVertex> getAdjacentVertices(BaseVertex vertex) {
        Set<BaseVertex> retSet = new HashSet<>();

        if (this.remVertexSet.contains(vertex)) {
            return retSet;
        }

        Set<BaseVertex> adjVertexSet = this.getAdjacentVerticesOfOriginGraph(vertex);
        for (BaseVertex currVertex : adjVertexSet) {
            if (this.remVertexSet.contains(currVertex)) {
                continue;
            }

            BaseEdge e = this.getEdgeInstanceOfOriginGraph(vertex, currVertex);
            if (this.remEdgeSet.contains(e)) {
                continue;
            }

            retSet.add(currVertex);
        }

        return retSet;
    }

    public Set<BaseVertex> getPrecedentVerticesOfOriginGraph(BaseVertex vertex) {
        return this.faninVerticesIndex.containsKey(vertex.getId())
                ? this.faninVerticesIndex.get(vertex.getId())
                : new HashSet<BaseVertex>();
    }

    @Override
    public Set<BaseVertex> getPrecedentVertices(BaseVertex vertex) {
        Set<BaseVertex> retSet = new HashSet<>();

        if (this.remVertexSet.contains(vertex)) {
            return retSet;
        }

        Set<BaseVertex> preVertexSet = this.getPrecedentVerticesOfOriginGraph(vertex);
        for (BaseVertex currVertex : preVertexSet) {
            if (this.remVertexSet.contains(currVertex)) {
                continue;
            }

            BaseEdge e = this.getEdgeInstanceOfOriginGraph(currVertex, vertex);
            if (this.remEdgeSet.contains(e)) {
                continue;
            }

            retSet.add(currVertex);
        }

        return retSet;
    }

    public List<BaseVertex> getVertexListOfOriginGraph() {
        return this.vertexList;
    }

    @Override
    public List<BaseVertex> getVertexList() {
        List<BaseVertex> retList = new ArrayList<>();
        for (BaseVertex currVertex : this.vertexList) {
            if (this.remVertexSet.contains(currVertex)) {
                continue;
            }

            retList.add(currVertex);
        }

        return retList;
    }

    public List<BaseEdge> getEdgeListOfOriginGraph() {
        return this.edgeList;
    }

    @Override
    public List<BaseEdge> getEdgeList() {
        List<BaseEdge> retList = new ArrayList<>();
        for (BaseEdge currEdge : this.edgeList) {
            if (this.remEdgeSet.contains(currEdge)) {
                continue;
            }

            retList.add(currEdge);
        }

        return retList;
    }

    public BaseVertex getVertexById(int id) {
        return this.idVertexIndex.get(id);
    }

    public BaseVertex getVertex(int id) {
        BaseVertex vertex = this.getVertexById(id);

        if (this.remVertexSet.contains(vertex)) {
            return null;
        }

        return vertex;
    }

    public BaseEdge getEdgeById(int id) {
        return this.idEdgeIndex.get(id);
    }

    public BaseEdge getEdge(int id) {
        BaseEdge edge = this.getEdgeById(id);

        if (this.remEdgeSet.contains(edge)) {
            return null;
        }

        return edge;
    }

    public int getVertexNum() {
        return this.vertexNum;
    }

    public int getEdgeNum() {
        return this.edgeNum;
    }

    public BaseEdge getEdgeInstanceOfOriginGraph(BaseVertex source, BaseVertex sink) {
        for (BaseEdge be : this.edgeList) {
            if (be.getStartVertex().equals(source)
                    && be.getEndVertex().equals(sink)) {
                return be;
            }
        }

        return null;
    }

    @Override
    public BaseEdge getEdgeInstance(BaseVertex source, BaseVertex sink) {
        if (this.remVertexSet.contains(source) || this.remVertexSet.contains(sink)) {
            return null;
        }

        BaseEdge edge = this.getEdgeInstanceOfOriginGraph(source, sink);
        if (this.remEdgeSet.contains(edge)) {
            return null;
        }

        return edge;
    }

    public double getEdgeWeightOfOriginGraph(BaseVertex source, BaseVertex sink) {
        for (BaseEdge be : this.edgeList) {
            if (be.getStartVertex().equals(source)
                && be.getEndVertex().equals(sink)) {
                return be.getWeight();
            }
        }

        return Graph.DISCONNECTED;
    }

    @Override
    public double getEdgeWeight(BaseVertex source, BaseVertex sink) {
        if (this.remVertexSet.contains(source) || this.remVertexSet.contains(sink)) {
            return Graph.DISCONNECTED;
        }

        BaseEdge edge = this.getEdgeInstanceOfOriginGraph(source, sink);
        if (this.remEdgeSet.contains(edge)) {
            return Graph.DISCONNECTED;
        }

        return edge.getWeight();
    }

    protected void addVertexInternal(BaseVertex vertex) {
        if (vertex == null)
            throw new IllegalArgumentException("The vertex does not exist.");

        this.vertexList.add(vertex);
        this.idVertexIndex.put(vertex.getId(), vertex);
        this.vertexNum++;
    }

    protected void addEdgeInternal(BaseEdge edge) {
        BaseVertex startVertex = edge.getStartVertex();
        BaseVertex endVertex = edge.getEndVertex();
        int startVertexId = edge.getStartVertex().getId();
        int endVertexId = edge.getEndVertex().getId();

        if (!idVertexIndex.containsKey(startVertexId)
            || !idVertexIndex.containsKey(endVertexId)
            || startVertexId == endVertexId) {
            throw new IllegalArgumentException("The edge from " + startVertexId +
                                                " to " + endVertexId + " does not exist.");
        }

        Set<BaseVertex> fanoutVertexSet = this.getAdjacentVertices(startVertex);
        fanoutVertexSet.add(endVertex);
        this.fanoutVerticesIndex.put(startVertexId, fanoutVertexSet);

        Set<BaseVertex> faninVertexSet = this.getPrecedentVertices(endVertex);
        faninVertexSet.add(startVertex);
        this.faninVerticesIndex.put(endVertexId, faninVertexSet);

        this.edgeList.add(edge);
        this.idEdgeIndex.put(edge.getId(), edge);
        this.edgeNum++;
    }

    public void deleteEdge(BaseEdge edge) {
        this.remEdgeSet.add(edge);
    }

    public void deleteVertex(BaseVertex vertex) {
        this.remVertexSet.add(vertex);
    }

    public void recoverDeletedVertices() {
        this.remVertexSet.clear();
    }

    public void recoverDeletedVertex(BaseVertex vertex) {
        this.remVertexSet.remove(vertex);
    }

    public void recoverDeletedEdges() {
        this.remEdgeSet.clear();
    }

    public void recoverDeletedEdge(BaseEdge edge) {
        this.remEdgeSet.remove(edge);
    }

    /*
    public void setDeleteVertexList(Collection<Integer> remVertexIds) {
        for (Integer id : remVertexIds) {
            BaseVertex vertex = this.getVertexById(id);
            this.remVertexSet.add(vertex);
        }
    }

    public void setDeleteEdgeList(Collection<Pair<Integer, Integer>> remEdgePairs) {
        for (Pair<Integer, Integer> pair : remEdgePairs) {
            BaseVertex startVertex = this.getVertexById(pair.first());
            BaseVertex endVertex = this.getVertexById(pair.second());
            BaseEdge edge = this.getEdgeInstanceOfOriginGraph(startVertex, endVertex);
            this.remEdgeSet.add(edge);
        }
    }
    */
}
